home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Eudora 1.3.1 / source / sendmail.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-03-16  |  29.6 KB  |  1,053 lines  |  [TEXT/MPS ]

  1. #define FILE_NUM 34
  2. /* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */
  3. /************************************************************************
  4.  * functions for dealing with a sendmail (or other?) smtp server
  5.  ************************************************************************/
  6. #pragma load EUDORA_LOAD
  7. #pragma segment SMTP
  8. typedef enum
  9. {
  10.     helo=1, mail, rcpt, data, rset, send, soml, saml,
  11.     vrfy, expn, help, noop, quit, turn
  12. } SMTPEnum;
  13.  
  14. #define CMD_BUFFER 1024
  15. #define IsAddressHead(head) (head==TO_HEAD || head==BCC_HEAD || head==CC_HEAD)
  16. typedef struct wdsEntry *WDSPtr;
  17. /************************************************************************
  18.  * declarations for private routines
  19.  ************************************************************************/
  20.     int DoIntroductions(void);
  21.     int SayByeBye(void);
  22.     int SendCmd(int cmd, UPtr args);
  23.     int SendCmdGetReply(int cmd, UPtr args, Boolean chatter);
  24.     int SendHeaderLine(int header,TEHandle teh);
  25.     int SendAttachments(TEHandle teh,Boolean doWrap,Boolean plainText);
  26.     int SMTPCmdError(int cmd, UPtr args, UPtr message);
  27.     void PrimeProgress(MessType **messH);
  28.     int WannaSend(MyWindowPtr win);
  29.     void GrabSignature(void);
  30.     short SendPlain(short vRef,long dirId,UPtr name,Boolean doWrap);
  31.     UPtr PriorityHeader(UPtr buffer,Byte priority);
  32. int sErr;
  33. UHandle Signature=nil;
  34. short SendEm(WDSPtr entries,WDSPtr *current);
  35. short AddWDS(WDSPtr entries, WDSPtr *current,Uptr bf,long len,short maxC);
  36.  
  37. /************************************************************************
  38.  * Public routines
  39.  ************************************************************************/
  40. /************************************************************************
  41.  * StartSMTP - initiate a connection with the specified SMTP server
  42.  ************************************************************************/
  43. int StartSMTP(UPtr serverName, short port)
  44. {
  45.     if (UUPCOut) sErr=UUPCPrime(serverName);
  46.     else if (!(sErr=ConnectTrans(serverName,port,False)))
  47.         sErr=DoIntroductions();
  48.     GrabSignature();
  49.     return(sErr);
  50. }
  51.  
  52. /************************************************************************
  53.  * SendMessage - send a message to the SMTP server
  54.  ************************************************************************/
  55. int SendMessage(TOCType **tocH,int sumNum)
  56. {
  57.     Str255 buffer;
  58.     MessHandle messH;
  59.     
  60.     /*
  61.      * handle open, dirty windows
  62.      */
  63.     if (!(messH=SaveB4Send(tocH,sumNum))) return(1);
  64.     
  65.     /*
  66.      * Log, if we must
  67.      */
  68.     GetWTitle((*messH)->win,buffer);
  69.     ComposeLogR(LOG_SEND,nil,SENDING,buffer);
  70.     
  71.     /*
  72.      * reset SMTP
  73.      */
  74.     sErr = SendCmdGetReply(rset,nil,True);
  75.     if (sErr/100 != 2) return(sErr);
  76.     sErr = 0;
  77.     
  78.     /*
  79.      * envelope
  80.      */
  81.     GetReturnAddr(buffer,False);
  82.     sErr = SendCmdGetReply(mail,buffer,True);
  83.     if (sErr/100 != 2) return(sErr);
  84.     sErr = 0;
  85.     
  86.     if (WrapWrong) OffsetWindow((*messH)->win);
  87.     
  88.     if (sErr=DoRcptTos(messH,True)) goto done;
  89.     
  90.     /*
  91.      * and the body
  92.      */
  93.     sErr = SendCmdGetReply(data,nil,True);
  94.     if (sErr/100 != 3) goto done;
  95.     sErr = 0;
  96.     
  97.     if (sErr=TransmitMessage(messH,True)) goto done;
  98.     
  99.     TimeStamp(tocH,sumNum,GMTDateTime(),ZoneSecs());
  100.             
  101. done:
  102.     CloseMyWindow((*messH)->win);
  103.     (void)ComposeLogR(LOG_SEND,nil,sErr?FAILED:SUCCEEDED,sErr);
  104.     return(sErr);
  105. }
  106.  
  107. /************************************************************************
  108.  * EndSMTP - done talking to SMTP
  109.  ************************************************************************/
  110. int EndSMTP()
  111. {
  112.     if (UUPCOut) UUPCDry();
  113.     else
  114.     {
  115.         if (sErr || CommandPeriod) SilenceTrans(True);
  116.         if ((!sErr || (sErr<600 && sErr>=400)) && !(sErr=SayByeBye()))
  117.             DisTrans();
  118.         sErr = DestroyTrans();
  119.     }
  120.     if (Signature) ZapHandle(Signature);
  121.     return(sErr);
  122. }
  123.  
  124. /************************************************************************
  125.  * SMTPError - return the last SMTP error
  126.  ************************************************************************/
  127. int SMTPError(void)
  128. {
  129.     return (sErr);
  130. }
  131.  
  132. /************************************************************************
  133.  * Private routines
  134.  ************************************************************************/
  135. /************************************************************************
  136.  * DoIntroductions - take care of the beginning of the SMTP protocol
  137.  ************************************************************************/
  138. int DoIntroductions(void)
  139. {
  140.     Str255 buffer;
  141.     
  142.     /*
  143.      * get banner from the remote end
  144.      */
  145.     sErr = GetReply(buffer,sizeof(buffer),True);
  146.     if (sErr/100 != 2) return(sErr);
  147.     sErr = 0;
  148.     /*
  149.      * tell it who we are
  150.      */
  151.     sErr = SendCmdGetReply(helo,WhoAmI(buffer),True);
  152.     return((sErr/100 != 2) ? sErr :  (sErr=0));
  153. }
  154.  
  155. /************************************************************************
  156.  * SayByeBye - take care of the end of the SMTP protocol
  157.  ************************************************************************/
  158. int SayByeBye(void)
  159.     sErr = SendCmdGetReply(quit,nil,False);
  160.     return((sErr/100 != 2) ? sErr : (sErr=0));    
  161. }
  162.  
  163. /************************************************************************
  164.  * SendCmd - send an smtp command, with optional arguments
  165.  ************************************************************************/
  166. int SendCmd(int cmd, UPtr args)
  167. {
  168.     Byte buffer[CMD_BUFFER];
  169.  
  170.     GetRString(buffer,SMTP_STRN+cmd);
  171.     if (args && *args)
  172.         PCat(buffer,args);
  173.     Progress(NoBar,buffer); 
  174.     PCat(buffer,NewLine);
  175.     
  176.     if (sErr=SendTrans(1,buffer+1,*buffer)) return(sErr);
  177.     
  178.     return(noErr);
  179. }
  180.  
  181. /************************************************************************
  182.  * SMTPCmdError - report an error for an SMTP command
  183.  ************************************************************************/
  184. int SMTPCmdError(int cmd, UPtr args, UPtr message)
  185. {
  186.     Str255 theCmd;
  187.     Str255 theError;
  188.     int err;
  189.  
  190.     GetRString(theCmd,2800+cmd);
  191.     if (args && *args)
  192.         PCat(theCmd,args);
  193.     strcpy(theError+1,message);
  194.     *theError = strlen(theError+1);
  195.     if (theError[*theError]=='\012') (*theError)--;
  196.     if (theError[*theError]=='\015') (*theError)--;
  197.     MyParamText(theCmd,theError,"\pSMTP","");
  198.     err = ReallyDoAnAlert(PROTO_ERR_ALRT,Note);
  199.     return(err);
  200. }
  201.  
  202. /************************************************************************
  203.  * SendCmdGetReply - send an smtp command, with optional arguments, and
  204.  * wait for the reply.    Returns reply code.
  205.  ************************************************************************/
  206. int SendCmdGetReply(int cmd, UPtr args, Boolean chatter)
  207. {
  208.     char buffer[CMD_BUFFER];
  209.     
  210.     if (sErr=SendCmd(cmd,args)) return(601);            /* error in transmission */
  211.     sErr = GetReply(buffer,sizeof(buffer),False);
  212.     if (sErr>399 && sErr<=600 && sErr!=550 && chatter)
  213.         SMTPCmdError(cmd,args,buffer);
  214.     return(sErr);
  215. }
  216.  
  217. /************************************************************************
  218.  * GetReply - get a reply to an SMTP command
  219.  ************************************************************************/
  220. int GetReply(UPtr buffer, int size, Boolean verbose)
  221. {
  222.     long rSize;
  223.     Str127 scratch;
  224.     char *cp;
  225.     short err;
  226.     
  227.     do
  228.     {
  229.         rSize = size;
  230.         if (sErr=RecvLine(buffer,&rSize)) return(602);    /* error receiving */
  231.         if (verbose)
  232.         {
  233.             *scratch = MIN(127,rSize);
  234.             strncpy(scratch+1,buffer,*scratch);
  235.             Progress(NoChange,scratch);
  236.         }
  237.         for (cp=buffer;cp<buffer+rSize && (*cp < ' ' || *cp>'~');cp++);
  238.         rSize -= cp-buffer;
  239.     }
  240.     while (rSize<3 ||
  241.                  !isdigit(cp[0])||!isdigit(cp[1])||!isdigit(cp[2]) ||
  242.                                  rSize>3 && cp[3]=='-');
  243.     cp[rSize] = 0;
  244.     err = atoi(cp);
  245.     if (verbose && err>399 && err<600) SMTPCmdError(nil,nil,buffer);
  246.     return(err);
  247.  
  248. /************************************************************************
  249.  * DoRcptTos - tell the remote sendmail who is getting the message
  250.  ************************************************************************/
  251. int DoRcptTos(MessType **messH, Boolean chatter)
  252. {
  253.     sErr=DoRcptTosFrom((*messH)->txes[TO_HEAD-1],chatter);
  254.     if (sErr) return(sErr);
  255.     sErr=DoRcptTosFrom((*messH)->txes[BCC_HEAD-1],chatter); 
  256.     if (sErr) return(sErr);
  257.     sErr=DoRcptTosFrom((*messH)->txes[CC_HEAD-1],chatter);
  258.     return(sErr);
  259. }
  260.  
  261. /************************************************************************
  262.  * DoRcptTosFrom - do the Rcpt to's from a particular TERec
  263.  ************************************************************************/
  264. int DoRcptTosFrom(TEHandle teh, Boolean chatter)
  265. {
  266.     Str255 toWhom;
  267.     UHandle addresses,rawAddresses;
  268.     UPtr address;
  269.  
  270.     sErr = 550;
  271.     if (rawAddresses=SuckAddresses((*teh)->hText,(*teh)->teLength,False))
  272.     {
  273.         sErr=200;
  274.         if (**rawAddresses)
  275.         {
  276.             addresses = ExpandAliases(rawAddresses,0,False);
  277.             DisposHandle(rawAddresses);
  278.             if (!addresses) return(sErr=500);
  279.             for (address=LDRef(addresses); *address; address += *address + 2)
  280.             {
  281.                 if (*address > MAX_ALIAS)
  282.                 {
  283.                     WarnUser(BAD_ADDRESS,0);
  284.                     sErr = 550;
  285.                     break;
  286.                 }
  287.                 if (UUPCOut)
  288.                 {
  289.                     if (sErr=UUPCWriteAddr(address)) break;
  290.                 }
  291.                 else
  292.                 {
  293.                     toWhom[0] = 1; toWhom[1] = '<';
  294.                     PCat(toWhom,address);
  295.                     PCatC(toWhom,'>');
  296.                     sErr = SendCmdGetReply(rcpt,toWhom,chatter);
  297.                     if (chatter && sErr==550)
  298.                         AlertStr(BAD_ADDR_ALRT,Stop,address);
  299.                     if (sErr/100 != 2) break;
  300.                 }
  301.             }
  302.             DisposHandle(addresses);
  303.         }
  304.         else
  305.             DisposHandle(rawAddresses);
  306.     }
  307.     return(sErr/100!=2 ? sErr : (sErr=0));
  308. }
  309.  
  310. /************************************************************************
  311.  * TransmitMessage - send a message to the remote sendmail
  312.  ************************************************************************/
  313. int TransmitMessage(MessType **messH, Boolean chatter)
  314. {
  315.     int header;
  316.     Str255 buffer;
  317.     long flags=(*(*messH)->tocH)->sums[(*messH)->sumNum].flags;
  318.     TEHandle body = (*messH)->txes[HEAD_LIMIT-1];
  319.     Byte priority;
  320.         
  321.     PrimeProgress(messH);
  322.  
  323.     /*
  324.      * extra headers
  325.      */
  326.     if (GetResource('STR#',EX_HEADERS_STRN))
  327.     {
  328.         for (header=1;;header++)
  329.         {
  330.             if (*GetRString(buffer,EX_HEADERS_STRN+header))
  331.             {
  332.                 if (sErr=SendTrans(2,buffer+1,*buffer,NewLine+1,*NewLine))
  333.                     return(600);
  334.             }
  335.             else break;
  336.         }
  337.     }
  338.  
  339.     /*
  340.      * real headers
  341.      */
  342.     priority = (*(*messH)->tocH)->sums[(*messH)->sumNum].priority;
  343.     priority = Prior2Display(priority);
  344.     if (priority!=3)
  345.     {
  346.         PriorityHeader(buffer,priority);
  347.         if (sErr=SendTrans(2,buffer+1,*buffer,NewLine+1,*NewLine))
  348.           return(600);
  349.     }
  350.     
  351.     BuildDateHeader(buffer,0);
  352.     if (*buffer && (sErr=SendTrans(2,buffer+1,*buffer,NewLine+1,*NewLine)))
  353.         return(600);
  354.     
  355.     for (header=1;header<HEAD_LIMIT;header++)
  356.         if (header!=BCC_HEAD && (sErr=SendHeaderLine(header,(*messH)->txes[header-1])))
  357.             return(600);
  358.     GiveTime();
  359.     
  360.     /*
  361.      * body
  362.      */
  363.     SendTrans(1,NewLine+1,*NewLine);
  364.     
  365.     if (WrapWrong)
  366.     {
  367.         LDRef(body);
  368.         sErr=SendBodyLines((*body)->hText,(*body)->teLength,0,
  369.                                              (flags&FLAG_WRAP_OUT)!=0,True,(*body)->lineStarts,
  370.                                              (*body)->nLines,False);
  371.         UL(body);
  372.         if (sErr) return(600);
  373.     }
  374.     else if (sErr=SendBodyLines((*body)->hText,(*body)->teLength,0,
  375.                                                  (flags&FLAG_WRAP_OUT)!=0,True,nil,0,False))
  376.         return(600);
  377.  
  378.     if ((flags & FLAG_SIG) && Signature && (sErr=SendBodyLines(Signature,
  379.              GetHandleSize(Signature),0,(flags&FLAG_WRAP_OUT)!=0,True,nil,0,False)))
  380.         return(600);
  381.     
  382.     if (sErr=SendAttachments((*messH)->txes[ATTACH_HEAD-1],
  383.                                                      (flags&FLAG_WRAP_OUT)!=0,
  384.                                                      (flags&FLAG_BX_TEXT)==0))
  385.         return(600);
  386.         
  387.     if (!UUPCOut &&
  388.             (sErr=SendTrans(3,NewLine+1,*NewLine,".",1,NewLine+1,*NewLine)))
  389.         return(600);
  390.     Progress(100,nil);
  391.     
  392.     if (!UUPCOut)
  393.     {
  394.         sErr = GetReply(buffer,sizeof(buffer),False);
  395.         if (sErr > 399 && chatter)
  396.             SMTPCmdError(data,"",buffer);
  397.     }
  398.     
  399.     return(sErr/100 != 2 ? sErr : (sErr=0));
  400. }         
  401.  
  402. /************************************************************************
  403.  * PriorityHeader: Build a priority header
  404.  ************************************************************************/
  405. UPtr PriorityHeader(UPtr buffer,Byte priority)
  406. {
  407.     return(ComposeRString(buffer,PRIORITY_FMT,HEADER_STRN+PRIORITY_HEAD,priority,PRIOR_STRN+priority));
  408. }
  409.  
  410. /************************************************************************
  411.  * These macros will help send bunches of lines at a time through MacTCP
  412.  * Note that A/UX has VERY limited WDS capabilities.
  413.  * There is also a problem with LC's and LC Ethernet cards, around 16
  414.  * somewhere.
  415.  ************************************************************************/
  416. #define NENTRIES 32
  417. #define ENTRYDECLARATIONS                                                                                             \
  418.     struct wdsEntry entries[NENTRIES];                                                                        \
  419.     WDSPtr *current=entries;                                                                                            \
  420.     short lastC,lastLen;                                                                                                    \
  421.     int sendCount = IsAUX() ? 5 : MIN(GetRLong(WDS_LIMIT),NENTRIES)
  422. #define ADD(bf,len) \
  423.     do {lastLen = len;if ((lastC=AddWDS(entries,¤t,bf,len,sendCount))<0) goto done;} while(0)
  424. #define SEND() do {if (sErr=SendEm(entries,¤t)) goto done;} while(0)
  425.  
  426. /************************************************************************
  427.  * AddWDS - add a string to the current set of WDS buffers
  428.  ************************************************************************/
  429. short AddWDS(WDSPtr entries, WDSPtr *current,Uptr bf,long len,short maxC)
  430. {
  431.     short lastC;
  432.     (*current)->ptr=bf; (*current)->length=len;
  433.     lastC = (bf)[len-1];
  434.     if (len && ++(*current)-entries == maxC-1)
  435.         if (sErr=SendEm(entries,current)) return(-1);
  436.     return(lastC);
  437. }
  438.  
  439. /************************************************************************
  440.  * SendEm - send the current set of WDS buffers
  441.  ************************************************************************/
  442. short SendEm(WDSPtr entries,WDSPtr *current)
  443. {
  444.     short err=0;
  445.     WDSPtr this;
  446.     long bytes;
  447.     
  448.     CycleBalls();
  449.     for (this=entries;this<*current;this++) bytes+=this->length;
  450.     if (bytes)
  451.     {
  452.         ByteProgress(nil,-bytes,0);
  453.         (*current)->length = 0;
  454.         err=SendWDS(entries);
  455.         *current=entries;
  456.     }
  457.     GiveTime();
  458.     return(err);
  459. }
  460.  
  461. /************************************************************************
  462.  * SendHeaderLine - send a line of header information to sendmail
  463.  ************************************************************************/
  464. int SendHeaderLine(int header,TEHandle teh)
  465. {
  466.     Str63 label, note;
  467.     UPtr start, stop, end, space, limit;
  468.     int lineLimit = GetRLong(WRAP_LIMIT);
  469.     UHandle safe=nil;
  470.     ENTRYDECLARATIONS;
  471.  
  472.     if (!(*teh)->teLength && header!=TO_HEAD) return(noErr);/* nothing here */
  473.     
  474.     /*
  475.      * label the header block
  476.      */
  477.     GetRString(label,HEADER_STRN+header);
  478.     ADD(label+1,*label);
  479.     
  480.     /*
  481.      * is it an address header?
  482.      */
  483.     if (header==TO_HEAD && !(*teh)->teLength)
  484.     {
  485.         GetRString(note,BCC_ONLY);
  486.         ADD(note+1,*note);
  487.         ADD(NewLine+1,*NewLine);
  488.     }
  489.     else if (IsAddressHead(header))
  490.     {
  491.         UHandle addresses;
  492.         UHandle rawAddresses = SuckAddresses((*teh)->hText,(*teh)->teLength,True);
  493.         int charsOnLine=0;
  494.         if (rawAddresses)
  495.         {
  496.             if (**rawAddresses)
  497.             {
  498.                 addresses = ExpandAliases(rawAddresses,0,True);
  499.                 DisposHandle(rawAddresses);
  500.                 if (addresses)
  501.                 {
  502.                     for (start=LDRef(addresses); *start; start += *start+2)
  503.                     {
  504.                         if (lineLimit && charsOnLine && *start+charsOnLine>lineLimit)
  505.                         {
  506.                             ADD(",",1);
  507.                             ADD(NewLine+1,*NewLine);
  508.                             charsOnLine = 0;
  509.                         }
  510.                         if (charsOnLine)
  511.                             ADD(", ",2);
  512.                         else
  513.                             ADD(" ",1);
  514.                         ADD(start+1,*start);
  515.                         charsOnLine += 1+*start;
  516.                     }
  517.                     ADD(NewLine+1,*NewLine);
  518.                     SEND();
  519.                 }
  520.                 DisposHandle(addresses);
  521.             }
  522.             else
  523.                 DisposHandle(rawAddresses);
  524.         }
  525.     }
  526.     else if (header==FROM_HEAD)
  527.     {
  528.         Handle popCanon=nil,returnCanon=nil;
  529.         Str31 sender;
  530.         Str255 buffer;
  531.         static UPtr unverified = " (Unverified)";    /* deliberately code-embedded */
  532.         
  533.         /* send the return address */
  534.         ADD(" ",1);
  535.         ADD(LDRef((*teh)->hText),(*teh)->teLength);
  536.         
  537.         if (*Password)
  538.         {
  539.             /* figure out what the return addr means */
  540.             returnCanon = SuckPtrAddresses(*(*teh)->hText,(*teh)->teLength,False);
  541.             
  542.             /* grab the POP account, and figure out what it means */        
  543.             GetPref(buffer,PREF_POP);
  544.             popCanon = SuckPtrAddresses(buffer+1,*buffer,False);
  545.         }
  546.         
  547.         /* if different or no password, send Sender field */
  548.         if (!UUPCOut && (!POPSecure || !EqualString(LDRef(popCanon),LDRef(returnCanon),False,True)))
  549.         {
  550.             ADD(NewLine+1,*NewLine);    /* finish From: header */
  551.             GetPref(buffer,PREF_POP);
  552.             GetRString(sender,SENDER);
  553.             ADD(sender+1,*sender);        /* send sender line, but not newline */
  554.             if (!UUPCIn) ADD(buffer+1,*buffer);
  555.             if (!POPSecure) ADD(unverified,13);
  556.         }
  557.         ADD(NewLine+1,*NewLine);
  558.         SEND();
  559.  
  560.         DisposHandle(popCanon);
  561.         DisposHandle(returnCanon);
  562.     }
  563.     else
  564.     {
  565.         if (header==ATTACH_HEAD && (safe=NewHandle((*teh)->teLength)))
  566.             BlockMove(*(*teh)->hText,*safe,(*teh)->teLength);
  567.         /*
  568.          * send it, a line at a time
  569.          * prepend a ' ' to each line, ala RFC 822
  570.          */
  571.         start = LDRef((*teh)->hText);
  572.         stop = start + (*teh)->teLength;
  573.         for (;start<stop;start=end+1)
  574.         {
  575.             limit = start + lineLimit;
  576.             if (stop<limit) limit = stop;
  577.             for (space=end=start;end<limit && *end!='\n'; end++)
  578.                 if (*end==' ') space=end;
  579.             if (space>start && end >= limit && limit<stop) end = space;
  580.             ByteProgress(nil,-1,0);
  581.             ADD(" ",1); ADD(start,end-start); ADD(NewLine+1,*NewLine);
  582.         }
  583.     }
  584.     if (current!=entries) SEND();
  585.     if (safe)
  586.     {
  587.         BlockMove(*safe,*(*teh)->hText,(*teh)->teLength);
  588.         DisposHandle(safe);
  589.     }
  590. done:
  591.     UL((*teh)->hText);
  592.     return(sErr); 
  593. }
  594.  
  595. /************************************************************************
  596.  * SendBodyLines - send the actual body of the message
  597.  *    Don't look at this; it's a mess.
  598.  *    text                Handle to the text to send
  599.  *    length            length of same
  600.  *  offset            offset at which to begin
  601.  *    doWrap            should the text be wrapped?
  602.  *    forceLines    should I force a newline at the end of the text?
  603.  *    lineStarts    pointer to array for determining wrap (may be nil)
  604.  *    nLines            length of same
  605.  *    partial            should I listen to the partial information?
  606.  ************************************************************************/
  607. int SendBodyLines(UHandle  text,long length,long offset,Boolean doWrap,Boolean forceLines,short *lineStarts,short nLines,Boolean partial)
  608. {
  609.     UPtr start;            /* the beginning of the text left to be sent */
  610.     UPtr stop;            /* the end of the entire text block */
  611.     UPtr end;                /* one past the last character of a line of text to be sent
  612.                                          for complete lines, this will be a return */
  613.     UPtr space;            /* the last space before end */
  614.     UPtr limit;            /* the point at which the line should be wrapped (if any) */
  615.     int lineLimit;    /* # of chars at which to wrap */
  616.     int quoteLimit;    /* ditto for quoted lines */
  617.     static short quoteLevel;        /* the # of quote chars at start of line */
  618.     Byte suspendChar;                        /* the quote character */
  619.     static short partialSize;        /* the size of the last line output, if it
  620.                                                                  was an incomplete line */
  621.     static Boolean softNewline;    /* was the last newline added by us? */
  622.     Str31 scratch;
  623.     short i;
  624.     ENTRYDECLARATIONS;
  625.     
  626.     if (!partial)
  627.     {
  628.         softNewline=False;    /* the caller has told us not to */
  629.         partialSize = 0;        /* bother with partial processing */
  630.     }
  631.     start = LDRef(text)+offset;
  632.     stop = *text + length;
  633.  
  634.     /*
  635.      * gather up important info for wrap calculations
  636.      */
  637.     if (doWrap)
  638.     {
  639.         suspendChar = (GetRString(scratch,QUOTE_PREFIX))[1];
  640.         lineLimit = GetRLong(WRAP_LIMIT);
  641.         quoteLimit = GetRLong(QUOTE_LIMIT);
  642.         
  643.         /*
  644.          * if this is a new line, count the quote level
  645.          */
  646.         if (!partialSize)
  647.         {
  648.             for (end = start;*end==suspendChar && end<stop;end++);
  649.             quoteLevel = end-start;
  650.         }
  651.     }
  652.     
  653.     /*
  654.      * main loop; loop through the buffer, sending one line at a time
  655.      */
  656.     for (; start<stop; start = end+1)
  657.     {
  658.         /*
  659.          * Rong wrapping; based on the textedit linestarts array
  660.          */
  661.         if (doWrap && lineStarts)
  662.         {
  663.             if (nLines<=0) end=stop;        /* a single line */
  664.             else
  665.             {
  666.                 nLines--, lineStarts++;            /* point us at the next line */
  667.                 end = *text+*lineStarts-1;
  668.             }
  669.         }
  670.         
  671.         /*
  672.          * normal wrapping; based on line length
  673.          */
  674.         else
  675.         {
  676.             /* calculate the spot before which we should wrap */
  677.             if (doWrap)
  678.                 limit = start + (quoteLevel ? quoteLimit : lineLimit) - partialSize;
  679.             
  680.             /* if we don't want wrapping, or there is less text than the wrap limit */
  681.             if (!doWrap || stop<limit) limit = stop;    /* no need to wrap */
  682.             
  683.             /*
  684.              * look through the buffer, from start to the calculated line limit
  685.              * keep track of the last space we see, since it's a potential wrap point
  686.              * if we find a return, we have a whole line, and can send it
  687.              */
  688.             if (doWrap)
  689.             {
  690.                 for (space=end=start;end<limit && *end!='\n'; end++)
  691.                     if (*end==' ' && doWrap) space=end;
  692.                 if (end >= limit &&        /* we went over the wrap limit */
  693.                       limit<stop &&            /* it wasn't a bogus wrap limit */
  694.                         space>start)            /* and we found a space */
  695.                     end = space;                /* Wrap it! */
  696.             }
  697.             else
  698.             {
  699.                 for (end=start;end<limit && *end!='\n'; end++);    /* just look for newlines */
  700.             }
  701.             
  702.             /*
  703.              * make special allowance for lines >wrap limit but < 80
  704.              */
  705.             if (!softNewline && limit<stop && end-start+partialSize<quoteLimit)
  706.             {
  707.                 UPtr nl;
  708.                 for (nl=end;nl<start+partialSize+quoteLimit && *nl!='\n';nl++);
  709.                 if (*nl=='\n' && nl<stop) end=nl;        /* extend to hard return */
  710.             }
  711.          
  712.             /* are we adding the newline?  We'll want to know for the next line. */
  713.             if (end<limit) softNewline = *end!='\n';
  714.         }
  715.         
  716.         /*
  717.          * at this point, start points at the beginning of the line to send,
  718.          * and end points one character past the end of the line to send
  719.          */
  720.         
  721.         /* escape initial periods, if need be */ 
  722.         if (!partialSize && !UUPCOut && end>start && *start=='.') ADD(".",1);
  723.         
  724.         /* if there is data to send, send it */
  725.         if (end>start) ADD(start,end-start);
  726.         
  727.         /*
  728.          * send the newline, unless we've run out of characters and so don't know
  729.          * if this should be a complete line or not
  730.          */
  731.         if (forceLines || end<stop) ADD(NewLine+1,*NewLine);
  732.         
  733.         /*
  734.          * We just put out a line, so we know we're starting fresh for
  735.          * the next one, if there is a next one
  736.          */
  737.         partialSize = 0;
  738.  
  739.         /*
  740.          * quoted line processing, if there are any chars left
  741.          */
  742.         if (end<stop)
  743.             /*
  744.              * if we sent out a complete line, peek at the next line to see how
  745.              * many quote characters it has
  746.              */
  747.             if (*end=='\n')
  748.             {
  749.                 UPtr p;
  750.                 for (p=end+1;*p==suspendChar && p<stop;p++);
  751.                 quoteLevel = p-end-1;
  752.             }
  753.             else    /* if we wrapped it, prequote the next line */
  754.             {
  755.                 for (i=0;i<quoteLevel;i++) ADD(&suspendChar,1);
  756.                 partialSize = quoteLevel;    /* guess we have a partial line after all */
  757.             }
  758.         
  759.         /*
  760.          * normally, end points at a newline (for complete lines) or space
  761.          * (for wrapped ones).  So, we normally skip the character end points
  762.          * to.  However, long solid lines or Rong-wrapped lines might not obey
  763.          * this behavior; adjust end back by one to make up for the increment
  764.          * we'll do in just a few cycles...
  765.          */
  766.         if (end<stop && *end!=' ' && *end!='\n') end--;
  767.     }
  768.     
  769.     /*
  770.      * all done with that buffer.  If the last character is a newline,
  771.      * we don't have much to do.  Otherwise, we may (forceLines) wish to
  772.      * newline-terminate, else we want to remember how long the line
  773.      * fragment we sent was
  774.      */
  775.     if (lastC!=NewLine[*NewLine])
  776.       if (forceLines) ADD(NewLine+1,*NewLine);
  777.         else partialSize = lastLen;
  778.         
  779.     /*
  780.      * send the last buffer(s)
  781.      */
  782.     if (current != entries) SEND();
  783. done:
  784.     UL(text);
  785.     return(sErr); 
  786. }
  787.  
  788. /************************************************************************
  789.  * PrimeProgress - get the progress window started.
  790.  ************************************************************************/
  791. void PrimeProgress(MessType **messH)
  792. {
  793.     Str255 buff;
  794.     
  795.     *buff = 0;
  796.     PCat(buff,*((WindowPeek)(*messH)->win)->titleHandle);
  797.     ByteProgress(buff,0,CountCompBytes(messH));
  798. }
  799.  
  800.  
  801. /************************************************************************
  802.  * WannaSend - find out of the user wants to send a dirty window
  803.  ************************************************************************/
  804. int WannaSend(MyWindowPtr win)
  805. {
  806.     Str255 title;
  807.     
  808.     PCopy(title,*win->qWindow.titleHandle);
  809.     return(AlertStr(WANNA_SEND_ALRT,Stop,title));
  810. }
  811.  
  812. /************************************************************************
  813.  * SendAttachments - send the files the user has attached to his message.
  814.  ************************************************************************/
  815. int SendAttachments(TEHandle teh,Boolean doWrap,Boolean plainText)
  816. {
  817.     short colons[4];
  818.     short onColon;
  819.     Str31 name;
  820.     Str31 volName;
  821.     long id;
  822.     int err;
  823.     int onChar;
  824.     UHandle text = (*teh)->hText;
  825.     
  826.     onColon = 0;
  827.     for (onChar=0;onChar<(*teh)->teLength;onChar++)
  828.         if ((*text)[onChar] == ':')
  829.         {
  830.             colons[onColon] = onChar;
  831.             if (++onColon==sizeof(colons)/sizeof(short))
  832.             {
  833.                 BlockMove((*text)+colons[0]+1,volName+1,colons[1]-colons[0]);
  834.                 *volName = colons[1]-colons[0];
  835.                 id = atoi(LDRef(text)+colons[1]+1);
  836.                 BlockMove((*text)+colons[2]+1,name+1,colons[3]-colons[2]-1);
  837.                 *name = colons[3]-colons[2]-1;
  838.                 UL(text);
  839.                 if (plainText && IsText(volName,id,name))
  840.                     err = SendPlain(GetMyVR(volName),id,name,doWrap);
  841.                 else
  842.                     err = SendBinHex(GetMyVR(volName),id,name);
  843.                 if (err) return(err);
  844.                 onColon = 0;
  845.             }
  846.         }
  847.     return(noErr);
  848. }
  849.  
  850. /************************************************************************
  851.  * GrabSignature - read the signature file
  852.  ************************************************************************/
  853. void GrabSignature(void)
  854. {
  855.     Str31 name;
  856.     short refN,err,vRef;
  857.     long dirId;
  858.     long bytes;
  859.     
  860.  
  861.     if (err=GetFileByRef(SettingsRefN,&vRef,&dirId,name))
  862.         FileSystemError(READ_SETTINGS,name,err);
  863.     else if (err=FSHOpen(name,MyVRef,MyDirId,&refN,fsRdPerm))
  864.         FileSystemError(READ_SETTINGS,name,err);
  865.     else
  866.     {
  867.         if (err=GetEOF(refN,&bytes))
  868.             FileSystemError(READ_SETTINGS,name,err);
  869.         else if (bytes)
  870.         {
  871.             if (!(Signature = NuHandle(bytes)))
  872.                 WarnUser(MEM_ERR,err=MemError());
  873.             else
  874.             {
  875.                 if (err=FSRead(refN,&bytes,LDRef(Signature)))
  876.                 {
  877.                     FileSystemError(READ_SETTINGS,name,err);
  878.                     ZapHandle(Signature);
  879.                 }
  880.             }
  881.         }
  882.         FSClose(refN);
  883.     }
  884. }
  885. /************************************************************************
  886.  * SendPlain - send a plain text file
  887.  ************************************************************************/
  888. short SendPlain(short vRef,long dirId,UPtr name,Boolean doWrap)
  889. {
  890.     short refN=0;
  891.     UHandle dataBuffer=nil;
  892.     short dataSize;
  893.     int err;
  894.     short oldVol;
  895.     short newVol;
  896.     long fileSize,sendSize,readSize;
  897.     Str63 scratch;
  898.     Boolean partial = False;
  899.     
  900.     GetVol(scratch,&oldVol);
  901.     if (err=HSetVol(nil,vRef,dirId))
  902.         {FileSystemError(BINHEX_OPEN,name,err); goto done;}
  903.     GetVol(scratch,&newVol);
  904.     
  905.     /*
  906.      * allocate the buffers
  907.      */
  908.     dataSize = GetRLong(BUFFER_SIZE);
  909.     if (!(dataBuffer=NuHandle(dataSize)))
  910.         {WarnUser(MEM_ERR,MemError()); goto done;}
  911.  
  912.     ComposeRString(scratch,PLAIN_PROG_FMT,name);
  913.     PushProgress();
  914.     Progress(0,scratch);
  915.     
  916.     /*
  917.      * open it
  918.      */
  919.     if (err = FSHOpen(name,vRef,dirId,&refN,fsRdPerm))
  920.         {FileSystemError(BINHEX_OPEN,name,err); goto done;}
  921.     if (err = GetEOF(refN,&fileSize))
  922.         {FileSystemError(BINHEX_OPEN,name,err); goto done;}
  923.     
  924.     /*
  925.      * send it
  926.      */
  927.     ByteProgress(nil,0,fileSize);
  928.     for (;fileSize;fileSize-=readSize)
  929.     {
  930.         readSize=MIN(dataSize,fileSize);
  931.         sendSize = readSize;
  932.         if (err=FSRead(refN,&sendSize,LDRef(dataBuffer)))
  933.             {FileSystemError(BINHEX_READ,name,err); goto done;}
  934.         UL(dataBuffer);
  935.         if (SendBodyLines(dataBuffer,sendSize,0,doWrap,False,nil,0,partial))
  936.             goto done;
  937.         partial = (*dataBuffer)[sendSize-1] != '\n';
  938.     }
  939.     SendTrans(1,NewLine+1,*NewLine);
  940.     
  941.     
  942. done:
  943.     if (refN) FSClose(refN);
  944.     if (dataBuffer) DisposHandle(dataBuffer);
  945.     PopProgress(False);
  946.     SetVol(nil,oldVol);
  947.     return(err);    
  948. }             
  949.  
  950. /************************************************************************
  951.  * BuildDateHeader - build an RFC 822 date header
  952.  ************************************************************************/
  953. void BuildDateHeader(UPtr buffer,long seconds)
  954. {
  955.     DateTimeRec dtr;
  956.     long delta = ZoneSecs();
  957.     Boolean negative;
  958.     
  959.     if (delta==-1) {*buffer=0;return;}
  960.     if (seconds)
  961.         Secs2Date(seconds+delta,&dtr);
  962.     else
  963.         GetTime(&dtr);
  964.     if (negative=delta<0) delta *= -1;
  965.     delta /= 60; /* we want minutes */
  966.     ComposeRString(buffer,DATE_HEADER,
  967.                                             WEEKDAY_STRN+dtr.dayOfWeek,
  968.                                             dtr.day,
  969.                                             MONTH_STRN+dtr.month,
  970.                                             dtr.year,
  971.                                             dtr.hour/10, dtr.hour%10,
  972.                                             dtr.minute/10, dtr.minute%10,
  973.                                             dtr.second/10, dtr.second%10,
  974.                                             negative ? '-' : '+',
  975.                                             delta/600,(delta%600)/60,(delta%60)/10,delta%10);
  976.     return;
  977. }             
  978.  
  979. /************************************************************************
  980.  * SaveB4Send - grab an outgoing message, saving if necessary
  981.  ************************************************************************/
  982. MessHandle SaveB4Send(TOCHandle tocH,short sumNum)
  983. {
  984.     short which;
  985.     MessHandle messH = (MessHandle)(*tocH)->sums[sumNum].messH;
  986.     
  987.     if (messH && (*messH)->win->isDirty)
  988.     {
  989.         which = WannaSend((*messH)->win);
  990.         if (which == WANNA_SAVE_CANCEL || which==CANCEL_ITEM)
  991.             return (nil);
  992.         else if (which == WANNA_SAVE_SAVE && !SaveComp((*messH)->win))
  993.             return(nil);
  994.         else if (which == WANNA_SAVE_DISCARD)
  995.         {
  996.             (*messH)->win->isDirty = False;
  997.             CloseMyWindow((*messH)->win);
  998.             messH = nil;
  999.         }     
  1000.     }
  1001.     if (!messH)
  1002.     {
  1003.         if (!OpenComp(tocH,sumNum,nil,False)) return(nil);
  1004.         messH = (MessHandle)(*tocH)->sums[sumNum].messH;
  1005.     }
  1006.     return(messH);
  1007. }
  1008.  
  1009. /************************************************************************
  1010.  * TimeStamp - put a time stamp on a message
  1011.  ************************************************************************/
  1012. void TimeStamp(TOCHandle tocH,short sumNum,uLong when,long delta)
  1013. {
  1014.     PtrTimeStamp(LDRef(tocH)->sums+sumNum,when,delta);
  1015.     UL(tocH);
  1016.     CalcSumLengths(tocH,sumNum);
  1017.     InvalSum(tocH,sumNum);
  1018.     (*tocH)->dirty = True;
  1019. }
  1020.  
  1021. /************************************************************************
  1022.  * PtrTimeStamp - timestamp, but into a sum directly
  1023.  ************************************************************************/
  1024. void PtrTimeStamp(MSumPtr sum,uLong when,long delta)
  1025. {
  1026.     sum->seconds = when;
  1027.     if (when)
  1028.     {
  1029.         /* put the date into the summary */
  1030.         Str31 d1, d2, d3;
  1031.         Str63 fmt;
  1032.         long secs = when + delta;
  1033.         Boolean neg = delta < 0;
  1034.         
  1035.         IUTimeString(secs,False,d1);
  1036.         IUDateString(secs,shortDate,d2);
  1037.         
  1038.         if (neg) delta *= -1;
  1039.         delta /= 60;    /* minutes*/
  1040.         ComposeString(d3,"\p%c%d%d%d%d",neg?'-':'+',
  1041.                                             delta/600,(delta%600)/60,(delta%60)/10,delta%10);
  1042.  
  1043.         GetRString(fmt,DATE_SUM_FMT);
  1044.         if (d1[2]<'0' || d1[2]>'9') {BlockMove(d1+1,d1+2,*d1+1);++*d1;d1[1]=optSpace;}
  1045.         utl_PlugParams(fmt,sum->date,d1,d2,d3,nil);
  1046.     }
  1047.     else
  1048.       sum->date[0] = 0;
  1049. }
  1050.  
  1051.